﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProjectChords.Services.API;

namespace ProjectChords.Services.Impl.Memory
{
    /// <summary>
    /// Generic service that uses two dictionary based indices to perform
    /// basic CRUD operations as well as operations to get objects stored with
    /// a given constraint
    /// 
    /// Note:
    /// It may be confusing to see the word "constraint" everywhere. The specific
    /// uses can be seen in implemented subclasses.
    /// An example of "constraintId" for object obj of type ContentItem is:
    /// obj.Category.Id
    /// </summary>
    public abstract class GenericInMemoryService<T> : IGenericCRUDService<T>
    {
        private IGenericCRUDService<T> CRUDService { get; set; }
        protected Dictionary<string, IGenericCRUDService<T>> ConstraintIdToObjects { get; set; }

        public GenericInMemoryService()
        {
            this.CRUDService = new GenericCRUDServiceInMemory<T>();
            this.ConstraintIdToObjects = new Dictionary<string, IGenericCRUDService<T>>();
        }

        public T LoadFromId(string id)
        {
            return this.CRUDService.LoadFromId(id);
        }

        /// <summary>
        /// This method returns the unique identifier of the "constraint" object
        /// for given object.
        /// </summary>
        public abstract string GetConstraintValue(T obj);

        private void AddNewConstraintItem(string constraintItemId)
        {
            this.ConstraintIdToObjects.Add(constraintItemId, new GenericCRUDServiceInMemory<T>());
        }

        /// <summary>
        /// Returns an empty list if no matching constraint items are found.
        /// </summary>
        /// <param name="constraintId">Unique id of the "constraint" object</param>
        /// <returns>A list containing all objects that have matching constraint value</returns>
        public List<T> GetAllItemsForGivenConstraint(string constraintId)
        {
            if (this.ConstraintIdToObjects.ContainsKey(constraintId))
            {
                return this.ConstraintIdToObjects[constraintId].GetAll();
            }
            else
            {
                return new List<T>();
            }
        }

        public void Add(string id, T obj)
        {
            CRUDService.Add(id, obj);
            string constraintId = GetConstraintValue(obj);

            if (!this.ConstraintIdToObjects.ContainsKey(constraintId))
            {
                AddNewConstraintItem(constraintId);
            }
            this.ConstraintIdToObjects[constraintId].Add(id, obj);
        }

        public void Update(string id, T obj)
        {
            // Update is complex because the obj may have updated its Category!

            // case when the category is same.
            string constraintIdBeforeUpdate = GetConstraintValue(LoadFromId(id));
            if (constraintIdBeforeUpdate.Equals(GetConstraintValue(obj)))
            {
                ConstraintIdToObjects[constraintIdBeforeUpdate].Update(id, obj);
            }
            else
            {
                string constraintIdAfterUpdate = GetConstraintValue(obj);
                // Does the new category exist? If not, create one.
                if (!this.ConstraintIdToObjects.ContainsKey(constraintIdAfterUpdate))
                {
                    AddNewConstraintItem(constraintIdAfterUpdate);
                }

                // Perform the update: Remove from the Map of original category, add it to the new category.
                this.ConstraintIdToObjects[constraintIdBeforeUpdate].Delete(id);
                this.ConstraintIdToObjects[constraintIdAfterUpdate].Add(id, obj);
            }

            // Update the usual indices in any case.
            CRUDService.Update(id, obj);
        }

        public void Delete(string id)
        {
            string constraintId = GetConstraintValue(this.CRUDService.LoadFromId(id));
            this.CRUDService.Delete(id);
            this.ConstraintIdToObjects[constraintId].Delete(id);
        }

        public List<T> GetAll()
        {
            return CRUDService.GetAll();
        }

        public void ClearAll()
        {
            CRUDService.ClearAll();
            this.ConstraintIdToObjects.Clear();
        }
    }
}
